package com.zjun.widget;

import static ohos.multimodalinput.event.TouchEvent.POINT_MOVE;
import static ohos.multimodalinput.event.TouchEvent.PRIMARY_POINT_DOWN;
import static ohos.multimodalinput.event.TouchEvent.PRIMARY_POINT_UP;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ScrollHelper;
import ohos.agp.components.VelocityDetector;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

import java.lang.reflect.Field;
import java.util.List;

/**
 * Time rule view
 */
public class TimeRuleView extends Component {
    /**
     * MAX_TIME_VALUE
     */
    public static final int MAX_TIME_VALUE = 24 * 3600;
    /**
     * LOG_ENABLE
     */
    private static final boolean LOG_ENABLE = BuildConfig.DEBUG;
    /**
     * 最小单位对应的单位秒数值，一共四级: 10s、1min、5min、15min
     * 与 {@link #mPerTextCounts} 和 {@link #mPerCountScaleThresholds} 对应的索引值
     * <p>
     * 可以组合优化成数组
     */
    private static int[] mUnitSeconds = {
            10, 10, 10, 10, 60, 60
    };

    /**
     * 数值显示间隔。一共13级，第一级最大值，不包括
     */
    @SuppressWarnings("all")
    private static int[] mPerTextCounts = {
            60,
            60,
            2 * 60,
            4 * 60, // 10s/unit: 最大值, 1min, 2min, 4min
            5 * 60,
            10 * 60, // 1min/unit: 5min, 10min
            20 * 60,
            30 * 60, // 5min/unit: 20min, 30min
            3600,
            2 * 3600,
            3 * 3600,
            4 * 3600,
            5 * 3600,
            6 * 3600 // 15min/unit
    };
    private int bgColor;
    /**
     * 刻度颜色
     */
    private int gradationColor;
    /**
     * 时间块的高度
     */
    private float partHeight;
    /**
     * 时间块的颜色
     */
    private int partColor;
    /**
     * 刻度宽度
     */
    private float gradationWidth;
    /**
     * 秒、分、时刻度的长度
     */
    private float secondLen;

    private float minuteLen;
    private float hourLen;
    /**
     * 刻度数值颜色、大小、与时刻度的距离
     */
    private int gradationTextColor;

    private float gradationTextSize;
    private float gradationTextGap;

    /**
     * 当前时间，单位：s
     */
    private int currentTime;
    /**
     * 指针颜色
     */
    private int indicatorColor;
    /**
     * 指针上三角形的边长
     */
    private float indicatorTriangleSideLen;
    /**
     * 指针的宽度
     */
    private float indicatorWidth;
    /**
     * 与 {@link #mPerTextCounts} 对应的阈值，在此阈值与前一个阈值之间，则使用此阈值对应的间隔数值
     * 如：1.5f 代表 4*60 对应的阈值，如果 mScale >= 1.5f && mScale < 1.8f，则使用 4*60
     * <p>
     * 这些数值，都是估算出来的
     */
    @SuppressWarnings("all")
    private float[] mPerCountScaleThresholds = {
            6f,
            3.6f,
            1.8f,
            1.5f, // 10s/unit: 最大值, 1min, 2min, 4min
            0.8f,
            0.4f, // 1min/unit: 5min, 10min
            0.25f,
            0.125f, // 5min/unit: 20min, 30min
            0.07f,
            0.04f,
            0.03f,
            0.025f,
            0.02f,
            0.015f // 15min/unit: 1h, 2h, 3h, 4h, 5h, 6h
    };
    /**
     * 默认mScale为1
     */
    private float mScale = 1;
    /**
     * 1s对应的间隔，比较好估算
     */
    private final float mOneSecondGap = dp2px(12) / 60f;
    /**
     * 当前最小单位秒数值对应的间隔
     */
    private float mUnitGap = mOneSecondGap * 60;
    /**
     * 默认索引值
     */
    private int mPerTextCountIndex = 4;
    /**
     * 一格代表的秒数。默认1min
     */
    private int mUnitSecond = mUnitSeconds[mPerTextCountIndex];

    /**
     * 数值文字宽度的一半：时间格式为“00:00”，所以长度固定
     */
    private final float mTextHalfWidth;
    private final int SCROLL_SLOP;
    private final int MIN_VELOCITY;
    private final int MAX_VELOCITY;

    /**
     * 当前时间与 00:00 的距离值
     */
    private float mCurrentDistance;
    private Paint mPaint;
    private Paint mTextPaint;
    private Path mTrianglePath;
    private ScrollHelper mScroller;
    private VelocityDetector mVelocityTracker;
    private int mWidth;
    private int mHeight;
    private int mHalfWidth;
    private int mInitialX;
    private int mLastX;
    private int mLastY;
    private boolean isMoving;
    private boolean isScaling;
    private List<TimePart> mTimePartList;

    private OnTimeChangedListener mListener;

    /**
     * On time changed listener
     */
    public interface OnTimeChangedListener {
        void onTimeChanged(int newTimeValue);
    }

    /**
     * 时间片段
     */
    public static class TimePart {
        /**
         * 起始时间，单位：s，取值范围∈[0, 86399]
         * 0       —— 00:00:00
         * 86399   —— 23:59:59
         */
        public int startTime;

        /**
         * 结束时间，必须大于{@link #startTime}
         */
        public int endTime;
    }

    public TimeRuleView(Context context) {
        this(context, null);
    }

    public TimeRuleView(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    public TimeRuleView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initAttrs(attrSet);

        init(context);

        mTextHalfWidth = mTextPaint.measureText("00:00") * .5f;

        SCROLL_SLOP = 8;
        MIN_VELOCITY = 50;
        MAX_VELOCITY = 8000;

        calculateValues();

        addDrawTask(
                new DrawTask() {
                    @Override
                    public void onDraw(Component component, Canvas canvas) {
                        mWidth = component.getWidth();
                        mHeight = dp2px(60);
                        mHalfWidth = mWidth >> 1;
                        setWidth(mWidth);
                        setHeight(mHeight);

                        // 背景
                        Paint paintBg = new Paint();
                        paintBg.setColor(new Color(bgColor));
                        RectFloat rect = new RectFloat(0, 0, mWidth, mHeight);
                        canvas.drawRect(rect, paintBg);

                        // 刻度
                        drawRule(canvas);

                        // 时间段
                        drawTimeParts(canvas);

                        // 当前时间指针
                        drawTimeIndicator(canvas);
                    }
                });

        setTouchEventListener(
                new TouchEventListener() {
                    @Override
                    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                        final int action = touchEvent.getAction();
                        final int xCoordinate = (int) (int) touchEvent.getPointerPosition(0).getX();
                        final int yCoordinate = (int) touchEvent.getPointerPosition(0).getY();

                        if (mVelocityTracker == null) {
                            mVelocityTracker = VelocityDetector.obtainInstance();
                        }
                        mVelocityTracker.addEvent(touchEvent);
                        switch (action) {
                            case PRIMARY_POINT_DOWN:
                                isMoving = false;
                                mInitialX = xCoordinate;
                                if (!mScroller.isFinished()) {
                                    mScroller.abortAnimation();
                                }
                                break;
                            case POINT_MOVE:
                                if (isScaling) {
                                    break;
                                }
                                int dx = xCoordinate - mLastX;
                                if (!isMoving) {
                                    final int dy = yCoordinate - mLastY;
                                    if (Math.abs(xCoordinate - mInitialX) <= SCROLL_SLOP || Math.abs(dx) <= Math.abs(dy)) {
                                        break;
                                    }
                                    isMoving = true;
                                }
                                mCurrentDistance -= dx;
                                computeTime();
                                break;
                            case PRIMARY_POINT_UP:
                                if (isScaling || !isMoving) {
                                    break;
                                }
                                mVelocityTracker.calculateCurrentVelocity(1000, MAX_VELOCITY, MAX_VELOCITY);
                                final int xVelocity = (int) mVelocityTracker.getHorizontalVelocity();
                                if (Math.abs(xVelocity) >= MIN_VELOCITY) {
                                    // 惯性滑动
                                    final int maxDistance = (int) (MAX_TIME_VALUE / mUnitGap * mUnitGap);
                                    mScroller.doFling((int) mCurrentDistance, 0, -xVelocity, 0, 0, maxDistance, 0, 0);
                                    invalidate();
                                }
                                break;

                            default:
                                break;
                        }
                        mLastX = xCoordinate;
                        mLastY = yCoordinate;
                        return true;
                    }
                });
    }

    private void initAttrs(AttrSet attrs) {
        bgColor = AttrUtils.getColorFromAttr(attrs, "zjun_bgColor", Color.getIntColor("#EEEEEE"));
        gradationColor = AttrUtils.getColorFromAttr(attrs, "zjun_gradationColor", Color.GRAY.getValue());
        partHeight = AttrUtils.getDimensionFromAttr(attrs, "trv_partHeight", dp2px(20));
        partColor = AttrUtils.getColorFromAttr(attrs, "trv_partColor", Color.getIntColor("#F58D24"));
        gradationWidth = AttrUtils.getDimensionFromAttr(attrs, "trv_gradationWidth", 1);
        secondLen = AttrUtils.getDimensionFromAttr(attrs, "trv_secondLen", dp2px(3));
        minuteLen = AttrUtils.getDimensionFromAttr(attrs, "trv_minuteLen", dp2px(5));
        hourLen = AttrUtils.getDimensionFromAttr(attrs, "trv_hourLen", dp2px(10));
        gradationTextColor = AttrUtils.getColorFromAttr(attrs, "trv_gradationTextColor", Color.GRAY.getValue());
        gradationTextSize = AttrUtils.getDimensionFromAttr(attrs, "trv_gradationTextSize", sp2px(12));
        gradationTextGap = AttrUtils.getDimensionFromAttr(attrs, "trv_gradationTextGap", dp2px(2));
        currentTime = AttrUtils.getIntFromAttr(attrs, "trv_currentTime", 0);
        indicatorTriangleSideLen = AttrUtils.getDimensionFromAttr(attrs, "trv_indicatorTriangleSideLen", dp2px(15));
        indicatorWidth = AttrUtils.getDimensionFromAttr(attrs, "zjun_indicatorLineWidth", dp2px(1));
        indicatorColor = AttrUtils.getColorFromAttr(attrs, "zjun_indicatorLineColor", Color.RED.getValue());
    }

    private void calculateValues() {
        mCurrentDistance = currentTime / mUnitSecond * mUnitGap;
    }

    private void init(Context context) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize((int) gradationTextSize);
        mTextPaint.setColor(new Color(gradationTextColor));

        mTrianglePath = new Path();

        mScroller = new ScrollHelper();
    }

    private void computeTime() {
        // 不用转float，肯定能整除
        float maxDistance = MAX_TIME_VALUE / mUnitSecond * mUnitGap;
        // 限定范围
        mCurrentDistance = Math.min(maxDistance, Math.max(0, mCurrentDistance));
        currentTime = (int) (mCurrentDistance / mUnitGap * mUnitSecond);
        if (mListener != null) {
            mListener.onTimeChanged(currentTime);
        }
        invalidate();
    }

    /**
     * Draw rule *
     *
     * @param canvas canvas
     */
    private void drawRule(Canvas canvas) {
        // 移动画布坐标系
        canvas.save();
        canvas.translate(0, partHeight);
        mPaint.setColor(new Color(gradationColor));
        mPaint.setStrokeWidth(gradationWidth);

        // 刻度
        int start = 0;
        float offset = mHalfWidth - mCurrentDistance;
        final int perTextCount = mPerTextCounts[mPerTextCountIndex];
        while (start <= MAX_TIME_VALUE) {
            // 刻度
            if (start % 3600 == 0) {
                // 时刻度
                Point startP = new Point(offset, 0);
                Point endP = new Point(offset, hourLen);
                canvas.drawLine(startP, endP, mPaint);
            } else if (start % 60 == 0) {
                // 分刻度
                Point startP = new Point(offset, 0);
                Point endP = new Point(offset, minuteLen);
                canvas.drawLine(startP, endP, mPaint);
            } else {
                // 秒刻度
                Point startP = new Point(offset, 0);
                Point endP = new Point(offset, secondLen);
                canvas.drawLine(startP, endP, mPaint);
            }

            // 时间数值
            if (start % perTextCount == 0) {
                String text = formatTimeHHmm(start);
                canvas.drawText(
                        mTextPaint, text, offset - mTextHalfWidth, hourLen + gradationTextGap + gradationTextSize);
            }

            start += mUnitSecond;
            offset += mUnitGap;
        }
        canvas.restore();
    }

    /**
     * Draw time indicator *
     *
     * @param canvas canvas
     */
    private void drawTimeIndicator(Canvas canvas) {
        // 指针
        mPaint.setColor(new Color(indicatorColor));
        mPaint.setStrokeWidth(indicatorWidth);
        Point startP = new Point(mHalfWidth, 0);
        Point endP = new Point(mHalfWidth, mHeight);
        canvas.drawLine(startP, endP, mPaint);

        // 正三角形
        if (mTrianglePath.isEmpty()) {
            //
            final float halfSideLen = indicatorTriangleSideLen * .5f;
            mTrianglePath.moveTo(mHalfWidth - halfSideLen, 0);
            mTrianglePath.rLineTo(indicatorTriangleSideLen, 0);
            mTrianglePath.rLineTo(-halfSideLen, (float) (Math.sin(Math.toRadians(60)) * halfSideLen));
            mTrianglePath.close();
        }
        mPaint.setStrokeWidth(1);
        mPaint.setStyle(Paint.Style.FILL_STYLE);
        canvas.drawPath(mTrianglePath, mPaint);
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
    }

    /**
     * 绘制时间段
     *
     * @param canvas canvas
     */
    private void drawTimeParts(Canvas canvas) {
        if (mTimePartList == null) {
            return;
        }
        // 不用矩形，直接使用直线绘制
        mPaint.setStrokeWidth(partHeight);
        mPaint.setColor(new Color(partColor));
        float start;
        float end;
        final float halfPartHeight = partHeight * .5f;
        final float secondGap = mUnitGap / mUnitSecond;
        for (int i = 0, size = mTimePartList.size(); i < size; i++) {
            TimePart timePart = mTimePartList.get(i);
            start = mHalfWidth - mCurrentDistance + timePart.startTime * secondGap;
            end = mHalfWidth - mCurrentDistance + timePart.endTime * secondGap;
            Point startP = new Point(start, halfPartHeight);
            Point endP = new Point(end, halfPartHeight);
            canvas.drawLine(startP, endP, mPaint);
        }
    }

    /**
     * 格式化时间 HH:mm
     *
     * @param timeValue 具体时间值
     * @return 格式化后的字符串，eg：3600 to 01:00
     */
    public static String formatTimeHHmm(int timeValue) {
        int temTimeValue = timeValue;
        if (temTimeValue < 0) {
            temTimeValue = 0;
        }
        int hour = temTimeValue / 3600;
        int minute = temTimeValue % 3600 / 60;
        StringBuilder sb = new StringBuilder();
        if (hour < 10) {
            sb.append('0');
        }
        sb.append(hour).append(':');
        if (minute < 10) {
            sb.append('0');
        }
        sb.append(minute);
        return sb.toString();
    }

    /**
     * 格式化时间 HH:mm:ss
     *
     * @param timeValue 具体时间值
     * @return 格式化后的字符串，eg：3600 to 01:00:00
     */
    public static String formatTimeHHmmss(int timeValue) {
        int hour = timeValue / 3600;
        int minute = timeValue % 3600 / 60;
        int second = timeValue % 3600 % 60;
        StringBuilder sb = new StringBuilder();

        if (hour < 10) {
            sb.append('0');
        }
        sb.append(hour).append(':');

        if (minute < 10) {
            sb.append('0');
        }
        sb.append(minute);
        sb.append(':');

        if (second < 10) {
            sb.append('0');
        }
        sb.append(second);
        return sb.toString();
    }

    private int dp2px(float dp) {
        return DensityUtil.dp2px(getContext(), dp);
    }

    private int sp2px(float sp) {
        return DensityUtil.dp2px(getContext(), sp);
    }

    @SuppressWarnings("all")
    private void logD(String format, Object... args) {
        if (LOG_ENABLE) {
            LogUtil.info("TimeRuleView", String.format("zjun@" + format, args));
        }
    }

    /**
     * 设置时间变化监听事件
     *
     * @param listener 监听回调
     */
    public void setOnTimeChangedListener(OnTimeChangedListener listener) {
        this.mListener = listener;
    }

    /**
     * 设置时间块（段）集合
     *
     * @param timePartList 时间块集合
     */
    public void setTimePartList(List<TimePart> timePartList) {
        this.mTimePartList = timePartList;
        new EventHandler(EventRunner.getMainEventRunner())
                .postTask(
                        new Runnable() {
                            @Override
                            public void run() {
                                invalidate();
                            }
                        });
    }

    /**
     * 设置当前时间
     *
     * @param currentTime 当前时间
     */
    public void setCurrentTime(int currentTime) {
        this.currentTime = currentTime;
        calculateValues();
        new EventHandler(EventRunner.getMainEventRunner())
                .postTask(
                        new Runnable() {
                            @Override
                            public void run() {
                                invalidate();
                            }
                        });
    }
}
